package ua.yandex.collections;

public class MyLinkedList implements MyList {

    private int size;
    private Entry header;

    public MyLinkedList() {
        header = new Entry();
        header.element = null;
        header.next = header;
        header.previous = header;
        size = 0;
    }

    private static class Entry {
        private Object element;
        private Entry next;
        private Entry previous;

        Entry() {

        }

        Entry(Object element, Entry previous, Entry next) {
            this.element = element;
            this.previous = previous;
            this.next = next;
        }
    }

    private void indexValid(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index is invalid.");
        }
    }

    private Entry findEntry(int index) {
        Entry entryOfIndex = header;
        if (index < size >> 1) {
            for (int entryIndex = 0; entryIndex <= index; entryIndex++) {
                entryOfIndex = entryOfIndex.next;
            }
        } else {
            for (int entryIndex = size; entryIndex > index; entryIndex--) {
                entryOfIndex = entryOfIndex.previous;
            }
        }
        return entryOfIndex;
    }

    public void addFirst(Object e) {
        Entry newEntry = new Entry(e, header, header.next);
        header.next.previous = newEntry;
        header.next = newEntry;
        size++;
    }

    public void addLast(Object e) {
        Entry newEntry = new Entry(e, header.previous, header);
        header.previous.next = newEntry;
        header.previous = newEntry;
        size++;
    }


    @Override
    public void add(Object e) {
        addLast(e);
    }

    @Override
    public void add(int index, Object e) {
        indexValid(index);
        Entry entryOfIndex = findEntry(index);
        Entry newEntry = new Entry(e, entryOfIndex.previous, entryOfIndex);
        entryOfIndex.previous.next = newEntry;
        entryOfIndex.previous = newEntry;
        size++;
    }

    @Override
    public void addAll(Object[] c) {
        for (Object data : c) {
            addLast(data);
        }
    }

    @Override
    public void addAll(int index, Object[] c) {
        indexValid(index);
        Entry entryOfIndex = findEntry(index);
        Entry previousEntry = entryOfIndex.previous;
        for (Object data : c) {
            Entry newEntry = new Entry(data, previousEntry, null);
            previousEntry.next = newEntry;
            previousEntry = newEntry;
        }
        previousEntry.next = entryOfIndex;
        entryOfIndex.previous = previousEntry;
        size += c.length;
    }

    @Override
    public Object get(int index) {
        indexValid(index);
        return findEntry(index).element;
    }


    public Object getFirst() {
        int indexOfFirst = 0;
        indexValid(indexOfFirst);
        return header.next.element;
    }

    public Object getLast() {
        int indexOfLast = size - 1;
        indexValid(indexOfLast);
        return header.previous.element;
    }

    @Override
    public Object remove(int index) {
        indexValid(index);
        Entry entryOfIndex = findEntry(index);
        entryOfIndex.previous.next = entryOfIndex.next;
        entryOfIndex.next.previous = entryOfIndex.previous;
        --size;
        return entryOfIndex.element;
    }

    public Object removeFirst() {
        int indexOfFirst = 0;
        indexValid(indexOfFirst);
        Entry firstEntry = header.next;
        header.next = firstEntry.next;
        firstEntry.next.previous = header;
        --size;
        return firstEntry.element;
    }

    public Object removeLast() {
        int indexOfLast = size - 1;
        indexValid(indexOfLast);
        Entry lastEntry = header.previous;
        header.previous = lastEntry.previous;
        lastEntry.previous.next = header;
        --size;
        return lastEntry.element;
    }

    @Override
    public void set(int index, Object e) {
        indexValid(index);
        findEntry(index).element = e;
    }

    @Override
    public int indexOf(Object o) {
        if (o == null) {
            Entry entryOfIndex = header;
            for (int index = 0; index < size; index++) {
                entryOfIndex = entryOfIndex.next;
                if (entryOfIndex.element == null) {
                    return index;
                }
            }
        } else {
            Entry entryOfIndex = header;
            for (int index = 0; index < size; index++) {
                entryOfIndex = entryOfIndex.next;
                if (entryOfIndex.element.equals(o)) {
                    return index;
                }
            }
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        header.next = header;
        header.previous = header;
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Object[] toArray() {
        Object[] toArray = new Object[size];
        Entry entryOfIndex = header;
        for (int index = 0; index < size; index++) {
            entryOfIndex = entryOfIndex.next;
            toArray[index] = entryOfIndex.element;
        }
        return toArray;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("[");
        Entry entryOfIndex = header;
        for (int index = 0; index < size; index++) {
            entryOfIndex = entryOfIndex.next;
            if (entryOfIndex.element == null) {
                string.append("null");
            } else {
                string.append(entryOfIndex.element.toString());
            }
            if (index < size - 1) {
                string.append(", ");
            }
        }
        string.append("]");
        return string.toString();
    }
}
